package org.turbobytes.unikey.util;

import org.turbobytes.unikey.util.exception.ConfigurationException;

import java.lang.reflect.Field;

/**
 * Utility methods to ease the use of configuration related items.
 *
 * @author Ganeshji Marwaha
 * @since Aug 7, 2010
 */
public class ConfigurationUtil {
  /**
   * Returns a user readable exception message for configuration failures
   * @param className - to add as part of the message
   * @param fieldName - to add as part of the message
   * @param fieldType - to add as part of the message
   * @param actualValue - to add as part of the message
   * @return fully formed exception message for configuration
   */
  public static String getExceptionMessage(String className, String fieldName, Class<?> fieldType, String actualValue) {
    return new StringBuilder().append("Cannot set property ")
                              .append("<<").append(fieldName).append(">>")
                              .append(" on ").append(className).append(". ")
                              .append("Cannot convert ").append(actualValue)
                              .append(" to ").append(fieldType.getSimpleName())
                              .append(". Please provide a valid value").toString();
  }

  /**
   * Given the Object, FieldName and FieldValue, this method verifies whether the fieldValue can be set on the
   * given field and sets it if possible using reflection. It sets the values to the field only for 3 supported types
   * as of now. They are
   * 1. Numeric (byte, short, int, long, Byte, Integer, Short, Long)
   * 2. Boolean (boolean, Boolean)
   * 3. String
   * For any other type of field, an IllegalStateException is thrown as we see that as a developer error  
   * @param obj - object to the set the value into
   * @param fieldName - field name to set the value to inside the {@code obj} instance
   * @param fieldValue - value to set
   */
  public static void setConfigurationValue(Object obj, String fieldName, String fieldValue) {
    Field field = ReflectionUtil.getField(obj, fieldName, true);
    Class<?> fieldType = field.getType();
    try {
      if(isWholeNumberFieldType(fieldType)){
        // TODO: Validate Whole number here. Eg. Negative Values
        ReflectionUtil.setFieldValue(obj, field, Long.valueOf(fieldValue));
      } else if(fieldType == Boolean.class || field.getType() == boolean.class) {
        ReflectionUtil.setFieldValue(obj, field, Boolean.valueOf(fieldValue));
      } else if(fieldType == String.class) {
        ReflectionUtil.setFieldValue(obj, field, fieldValue);
      } else {
        throw new IllegalStateException("Field type is not one of Numeric, Boolean or String. It is " + fieldType);
      }
    } catch(Exception e) {
      throw new ConfigurationException(getExceptionMessage(obj.getClass().getSimpleName(), fieldName, fieldType, fieldValue));
    }
  }

  /**
   * Checks for all whole number classes in java and returns true if the given field type is one of them.
   * Used to restrict configuration values to whole numbers when that restriction is necessary 
   * @param fieldType - to check for if it is a whole number
   * @return if the given class represents a type compatible with whole numbers alone
   */
  public static boolean isWholeNumberFieldType(Class<?> fieldType) {
    Assert.isNotNull(fieldType);
    return (fieldType == Byte.class || fieldType == byte.class ||
            fieldType == Short.class || fieldType == short.class ||
            fieldType == Integer.class || fieldType == int.class ||
            fieldType == Long.class) || fieldType == long.class;
  }
}
